728x90
728x90
728x90
728x90

문제인식

태블릿 PC를 구매하게 되었고 도서를 스캔하며 공부할 계획을 세웠다.

vFlat으로 스캔을 해보았지만 문제가 있었다. (물론 내 잘못)

한 번에 두 페이지를 스캔했기 때문에 각 페이지를 개별적으로 분할해야 했다.

물론 vFlat에는 두 페이지를 동시에 촬영하고 한 장씩 저장하는 기능이 있었지만, 유료 서비스였다.

매달 결제하고 싶지 않았기에 Python 코딩으로 해결하고자 했다.

 

code

마음에 들지는 않았지만, 그래도 그럭저럭 볼 만하게 분할되었다.

시간이 걸리더라도 한 번에 두 장씩 말고 한 장씩 스캔하는 것이 좋을 듯하다.

import os
from tkinter import Tk
from tkinter import filedialog

import cv2
import numpy as np


def split_scanned_pages(image_paths):
    for image_path in image_paths:
        # 이미지 읽기
        normalized_path = os.path.normpath(image_path)
        image = cv2.imread(normalized_path)
        if image is None:
            print(f"이미지를 불러올 수 없습니다: {normalized_path}")
            continue

        # 이미지 크기 얻기
        height, width, _ = image.shape

        # 가로 세로 비율을 기반으로 한 페이지 여부 판단
        aspect_ratio = width / height
        if aspect_ratio < 1.2:
            print(f"한 페이지로 간주됨, 분할하지 않습니다: {normalized_path}")
            # 한 페이지로 간주되면 복사하여 저장
            dir_name, file_name = os.path.split(image_path)
            left_page_path = os.path.join(dir_name, f"split_{os.path.splitext(file_name)[0]}_1{os.path.splitext(file_name)[1]}")
            cv2.imwrite(left_page_path, image)
            print(f"이미지 저장 완료: {left_page_path}")
            continue

        # 그레이스케일로 변환
        gray = cv2.cvtColor(image, cv2.COLOR_BGR2GRAY)

        # 가우시안 블러 적용 (노이즈 제거)
        blurred = cv2.GaussianBlur(gray, (5, 5), 0)

        # Adaptive Threshold 적용 (그림자 및 텍스트 영역 분리)
        adaptive_thresh = cv2.adaptiveThreshold(blurred, 255, cv2.ADAPTIVE_THRESH_GAUSSIAN_C, cv2.THRESH_BINARY_INV, 11, 2)

        # Sobel Edge Detection 적용 (가장자리 검출)
        sobelx = cv2.Sobel(blurred, cv2.CV_64F, 1, 0, ksize=5)
        sobely = cv2.Sobel(blurred, cv2.CV_64F, 0, 1, ksize=5)
        edges = cv2.magnitude(sobelx, sobely)

        # Morphological Closing 적용 (가장자리 연결 강화)
        kernel = np.ones((5, 5), np.uint8)
        edges = cv2.morphologyEx(edges, cv2.MORPH_CLOSE, kernel)

        # 세로 히스토그램 계산 (가장자리 합계 및 Adaptive Threshold 결과를 결합하여 수직 분포 확인)
        combined = np.sum(edges, axis=0) + np.sum(adaptive_thresh, axis=0)

        # 1D 가우시안 스무딩 적용 (히스토그램 스무딩)
        smoothed_combined = cv2.GaussianBlur(combined.reshape(1, -1), (1, 21), 0).flatten()

        # 중앙에서 밀도가 가장 낮은 지점을 찾기 위한 탐색 범위 설정 (47.5% ~ 52.5%)
        search_start = int(width * 0.475)
        search_end = int(width * 0.525)
        split_x = search_start
        min_density = float('inf')

        # 중앙 근처에서 밀도가 가장 낮은 지점 찾기
        for i in range(search_start, search_end):
            if smoothed_combined[i] < min_density:
                min_density = smoothed_combined[i]
                split_x = i

        # 컨투어 기반 추가 검토 (가장 큰 컨투어의 중심을 분할 지점으로 설정)
        contours, _ = cv2.findContours(adaptive_thresh, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE)
        if contours:
            largest_contour = max(contours, key=cv2.contourArea)
            x, y, w, h = cv2.boundingRect(largest_contour)
            contour_center = x + w // 2
            if search_start < contour_center < search_end:
                split_x = contour_center

        # 파일명 및 확장자 분리
        dir_name, file_name = os.path.split(image_path)
        base_name, ext = os.path.splitext(file_name)

        # 이미지를 두 개로 나누기
        left_page = image[:, :split_x]
        right_page = image[:, split_x:]

        # 분할된 이미지가 비어 있는지 확인
        if left_page.size == 0 or right_page.size == 0:
            print(f"분할된 이미지가 비어 있습니다. 분할을 건너뜁니다: {normalized_path}")
            continue

        # 저장할 파일명 설정
        left_page_path = os.path.join(dir_name, f"split_{base_name}_1{ext}")
        right_page_path = os.path.join(dir_name, f"split_{base_name}_2{ext}")

        # 결과 저장
        cv2.imwrite(left_page_path, left_page)
        cv2.imwrite(right_page_path, right_page)

        print(f"이미지 저장 완료: {left_page_path}, {right_page_path}")


# 파일 선택창 열기

Tk().withdraw()  # Tkinter 기본 창 숨기기
file_paths = filedialog.askopenfilenames(initialdir='D:/', title='이미지 파일 선택', filetypes=[("Image Files", "*.jpg *.jpeg *.png *.bmp")])

# 선택된 파일 처리
if file_paths:
    split_scanned_pages(file_paths)
else:
    print("파일이 선택되지 않았습니다.")
728x90
728x90
728x90
728x90

여러장의 이미지 파일을 pdf로 만드는 건 인터넷을 뒤져보면 많은 곳에서 서비스를 하고있습니다.

 

이러한 사이트를 이용할 때 불편함을 느끼는 경우가 많습니다.

첫째, 개인정보가 담긴 이미지을 사용할 경우 보안에 대한 걱정이 생깁니다.

둘째, 이미지 장수 제한이나 변환 횟수 제한으로 사용에 제약이 생기며, 결국 유료 결제를 유도하기도 합니다.

이러한 불편함 없이, 간단하고 자유롭게 이미지를 PDF로 변환할 수 있는 방법을 소개해 보겠습니다.

 

coding

스캔한 이미지를 도서 별로 압축하고, 해당 압축 파일을 선택하여 압축 파일이 있는 위치에 PDF를 생성합니다.

변환 과정에서 진행도가 출력되도록 하여 사용자에게 진행 상황을 명확히 보여줍니다.

여러 개의 압축 파일을 한 번에 선택해 PDF로 변환할 수 있도록 하여 효율성을 높였습니다.

import zipfile
import os
from PIL import Image
from tkinter import Tk, filedialog
import sys
import shutil


def convert_images_in_zip_to_pdf(zip_path, output_pdf_path):
    # 임시 디렉토리 생성
    temp_dir = "temp_images"
    if not os.path.exists(temp_dir):
        os.makedirs(temp_dir)

    # 압축 파일 열기
    with zipfile.ZipFile(zip_path, 'r') as zip_ref:
        zip_ref.extractall(temp_dir)

    # 이미지 파일들 가져오기
    image_files = [os.path.join(temp_dir, f) for f in os.listdir(temp_dir) if f.lower().endswith(('.png', '.jpg', '.jpeg', '.bmp', '.gif'))]
    total_files = len(image_files)

    # 이미지 파일들을 로드하여 PDF로 변환
    images = []
    for idx, file in enumerate(sorted(image_files)):
        # 진행도 출력
        progress = (idx + 1) / total_files * 100
        sys.stdout.write(f"\rProcessing images: {progress:.2f}% complete")
        sys.stdout.flush()

        img = Image.open(file)
        # 이미지가 'RGB' 모드가 아니면 변환
        if img.mode != 'RGB':
            img = img.convert('RGB')
        images.append(img)

    # 첫 번째 이미지를 기준으로 PDF 생성
    if images:
        images[0].save(output_pdf_path, save_all=True, append_images=images[1:])
    else:
        print("No images found in the zip file.")

    # 임시 디렉토리 삭제
    shutil.rmtree(temp_dir)


Tk().withdraw()  # GUI 창 숨기기
initial_dir = 'D:\\' if os.path.exists('D:\\') else None
zip_paths = filedialog.askopenfilenames(title="Select ZIP Files", filetypes=[("ZIP files", "*.zip")], initialdir=initial_dir)  # 여러 압축 파일 선택

if zip_paths:
    total_zips = len(zip_paths)
    for idx, zip_path in enumerate(zip_paths):
        # 압축 파일별 진행도 출력
        print(f"\nProcessing ZIP file {idx + 1} of {total_zips}: {zip_path}")
        output_pdf_path = os.path.splitext(zip_path)[0] + ".pdf"  # 출력 PDF 파일 경로를 압축 파일 경로와 동일하게 설정
        convert_images_in_zip_to_pdf(zip_path, output_pdf_path)
        print(f"\nPDF 파일이 성공적으로 '{output_pdf_path}'에 저장되었습니다.")
else:
    print("압축 파일을 선택하지 않았습니다.")

 

 

 

728x90
728x90
728x90
728x90

[URL github 링크]

모 회사에서 링크 단축 서비스를 구현하라는 코딩 테스트 과제를 받았습니다.

 

DB 구성

확장성을 고려하고 사용자가 많아 질 것을 고려하라는 단서를 주었고

확장성을 고려해 postgresql를 선택, 사용자가 많아질 것을 고려해 응답을 빠르게 할 수 있는 redis를 선택했습니다.

 

단축 알고리즘

검색 해보니 URL을 알파벳 대문자 + 소문자 + 숫자로 된 62진법으로 변환하는 것이 대중적이였습니다.

보안성과 효율성을 고려해야 하므로 URL에 Hash 알고리즘에 salt를 적용하기로 했습니다.

def base62_encode(num: int) -> str:
    base62_chars = string.ascii_letters + string.digits
    if num == 0:
        return base62_chars[0]
    base62 = []
    while num:
        num, rem = divmod(num, 62)
        base62.append(base62_chars[rem])
    return ''.join(reversed(base62))


def generate_short_key(url: str) -> str:
    return base62_encode(int(hashlib.sha256((url + os.urandom(16).hex()).encode()).hexdigest()[:12], 16))

 

URL에 길이 16의 랜덤 문자를 적용하여 Hash 값의 맨 앞 12자리만 사용하기로 했습니다.

 

URL 표준화

과제 요청사항에는 없었지만 https://www.youtube.com/https://www.youtube.com 또는

http://www.youtube.comwww.youtube.com가 같은 단축 값을 return 해야 한다고 생각해 URL 표준화를 하기로 했습니다.

def standardize_url(url: str) -> str:
    if not urlparse(url).scheme:
        url = 'http://' + url
    parsed_url = urlparse(url)
    netloc = parsed_url.netloc.replace('www.', '') if parsed_url.netloc.startswith('www.') else parsed_url.netloc
    path = parsed_url.path or '/'
    standardized_url = urlunparse((parsed_url.scheme, netloc, path, '', parsed_url.query, ''))
    return standardized_url.rstrip('/')

 

스케쥴러

추가 구현으로 각 단축 URL에 유효 기간을 기록 후 유효 기간이 지난  데이터는 삭제를 하라고 했습니다.

유효 기간이 지난 후 데이터 삭제를 위해 pg_cron을 써도 되지만 python 스케쥴러를 사용하고 싶었습니다.

Python 스케줄러는 Python 코드 내에서 직접 실행할 수 있어, FashAPI로 구현된 URL 단축 서비스 Python 환경에서 쉽게 관리할 수 있기 때문입니다.

def delete_expired_urls():
    db = SessionLocal()
    try:
        now = datetime.now()
        db.query(models.URL).filter(models.URL.expires_at <= now).delete()
        db.commit()
        print(f"Expired URLs deleted at {now}")
    except Exception as e:
        print(f"An error occurred while deleting expired URLs: {e}")
    finally:
        db.close()

 

브라우저 캐싱

URL 조회수 기능을 구현했지만 브라우저에서 동일한 short_url을 반복적으로 요청할 경우, 브라우저의 캐싱된 정보로 인해 서버를 거치지 않아 count 값이 증가하지 않습니다. 이를 해결하기 위해 브라우저나 캐시 서버가 응답을 캐시하지 않도록 지시했습니다.

response.headers["Cache-Control"] = "no-store, no-cache, must-revalidate, max-age=0"
response.headers["Pragma"] = "no-cache"

 

조언은 언제나 환영입니다.

728x90
728x90
728x90
728x90

5일, 10일, 20일 이동평균선 값으로 예약 구매를 하는 코드 작성하기 위해 특정 값을 가능한 가까운 호가로 변환하는 함수가 필요했습니다.

여러 생각끝에 다음의 조건을 만족하는 함수를 작성해봤습니다.

1. 입력 받은 price를 호가 단위에 맞는 가격으로 수정해서 return 한다.

2. number를 입력받아 1호가, 2호가, 3호가 등 호가 단위를 수정한다.

 

처음엔 다음과 같이 코딩을 했었는데

def price_refine(price: int, number: int = 1) -> int:
    if price < 2000:
        return price
    elif price < 5000:
        return int(5 * (int(price / 5) + number))
    elif price < 20000:
        return int(10 * (int(price / 10) + number))
    elif price < 50000:
        return int(50 * (int(price / 50) + number))
    elif price < 200000:
        return int(100 * (int(price / 100) + number))
    elif price < 500000:
        return int(500 * (int(price / 500) + number))
    else:
        return int(1000 * (int(price / 1000) + number))

 

이 함수는 호가를 정확히 구할 수 없었습니다.

예를 들어 price=19998, number=5일 경우 19998의 5번째 호가를 구하는 것인데 해당 함수의 return은 20040인데 원하는 결과는 20200입니다.

price가 20000부터 호가 단위가 변경되기 때문에 함수를 수정할 필요가 있었습니다.

 

def price_refine(price: int, number: int = 0) -> int:
    PRICE_LEVELS = [(2000, 1), (5000, 5), (20000, 10), (50000, 50), (200000, 100), (500000, 500), (float('inf'), 1000)]

    if number == 0:
        for level_price, adjustment in PRICE_LEVELS:
            if price < level_price or level_price == float('inf'):
                return round(price / adjustment) * adjustment

    increase = number > 0
    number_of_adjustments = abs(number)

    for _ in range(number_of_adjustments):
        for level_price, adjustment in PRICE_LEVELS:
            if (increase and price < level_price) or level_price == float('inf'):
                price = (math.trunc(price / adjustment) + 1) * adjustment
                break
            elif (not increase and price <= level_price) or level_price == float('inf'):
                price = (math.ceil(price / adjustment) - 1) * adjustment
                break

    return price

 

함수를 위와 같이 수정했습니다.

기본 가격 조정: number가 0일 경우, price는 가장 가까운 호가로 수정됩니다.
예를 들어, price가 4500일 때 PRICE_LEVELS에 따라 5000 미만이므로, 5로 나눈 후 가장 가까운 정수로 반올림하여 다시 5를 곱해 반올림 처리합니다 (즉, 4500은 4500으로 유지).


가격 증감 조정: number가 0이 아닐 때, number의 절대값만큼 가격을 증가시키거나 감소시킵니다.
number가 양수일 경우 (increase = True), 가격을 증가시킵니다. price를 해당 단계의 adjustment로 나눈 후 내림 처리하고, 1을 더한 후 다시 adjustment를 곱해 증가시킵니다.
number가 음수일 경우 (increase = False), 가격을 감소시킵니다. price를 adjustment로 나눈 후 올림 처리하고, 1을 빼고 adjustment를 곱해 감소시킵니다.
각 반복마다 가격을 새로 조정하고, 새로운 가격이 다음 level_price를 초과하지 않을 때까지 이 과정을 반복합니다.

 

이 함수도 for문이 중첩되어 있어 효율적이지 않아보였습니다.

PRICE_LEVELS에 따라 adjustment가 결정되면 다음 PRICE_LEVELS까지 같은 값을 올리기 때문에 좀 더 효율적으로 수정해보았습니다.

 

def price_refine(price: int, number: int = 0) -> int:
    PRICE_LEVELS = [(2000, 1), (5000, 5), (20000, 10), (50000, 50), (200000, 100), (500000, 500), (float('inf'), 1000)]

    for level_price, adjustment in PRICE_LEVELS:
        if price < level_price or level_price == float('inf'):
            adjusted_price = round(price / adjustment) * adjustment
            if (number > 0 and price < adjusted_price) or (number < 0 and price > adjusted_price):
                number += -1 if number > 0 else 1
            price = adjusted_price
            break

    increase = number > 0
    number_of_adjustments = abs(number)

    index = 0
    while number_of_adjustments > 0:
        level_price, adjustment = PRICE_LEVELS[index]
        if level_price == float('inf') or (increase and price < level_price) or (not increase and price <= level_price):
            max_steps_within_level = min(number_of_adjustments, math.ceil((level_price - price) / adjustment))
            if not increase:
                max_steps_within_level = min(number_of_adjustments, math.ceil((price - PRICE_LEVELS[index - 1][0]) / adjustment))

            price += (adjustment * max_steps_within_level * (1 if increase else -1))
            number_of_adjustments -= max_steps_within_level
        index += 1

    return price

 

위와 같이 수정하여 반복문 횟수를 줄였습니다만 가장 효율적인지는 모르겠습니다.

또한 효율적인 것도 좋지만 가독성도 좋아야 되는데 뭔가 복잡한 느낌입니다.

728x90
728x90
728x90
728x90

HTTP (Hypertext Transfer Protocol) is a protocol used for communication between web clients and servers. It is a fundamental protocol used by the World Wide Web to transmit data and resources such as web pages, images, videos, and other media.

One of the most important features of HTTP is the ability to send data with a specific format or type, known as the Content-Type. The Content-Type header is used to indicate the media type of the resource being sent. It is specified in the HTTP response header when a server sends data to a client. The client then uses this information to determine how to handle the data.

The format of the Content-Type header is as follows:

Content-Type: media-type

The media-type is a string that specifies the type of data being sent. For example, text/html is used to indicate an HTML document, image/jpeg is used to indicate a JPEG image, and application/json is used to indicate a JSON data format.

There are several different types of media-types, each with its own unique format and usage. Some common media types include:

- text/plain: used for plain text documents
- text/html: used for HTML documents
- application/json: used for JSON data format
- application/xml: used for XML documents
- image/png: used for PNG images
- image/jpeg: used for JPEG images
- audio/mpeg: used for MP3 audio files
- video/mp4: used for MP4 video files

The Content-Type header is essential for ensuring that data is sent and received in the correct format. It is also crucial for ensuring that the client knows how to handle the data and what to expect. If the server sends data with an incorrect Content-Type header, it can lead to errors and unexpected behavior on the client-side.

In conclusion, the Content-Type header is a crucial aspect of the HTTP protocol. It helps ensure that data is transmitted correctly and that clients can handle the data appropriately. As such, web developers must understand and use the Content-Type header correctly to ensure that their websites and web applications function correctly.

728x90
728x90
728x90
728x90

SQL injection is a type of attack on web applications that allows attackers to execute malicious SQL statements, usually by exploiting vulnerabilities in the application's input validation mechanisms. This type of attack can be devastating, as it can give the attacker full control over the database and access to sensitive information.

In this blog post, we will explore what SQL injection is, how it works, and what steps you can take to prevent it.

What is SQL Injection?

SQL injection is a type of attack that targets web applications that use SQL databases. The attack is carried out by inserting malicious SQL statements into the application's input fields. These input fields are usually used to gather data from users, such as usernames and passwords, and they are often used to build SQL queries that retrieve or modify data in the database.

When an application is vulnerable to SQL injection, an attacker can use these input fields to inject malicious SQL code into the application's database query. This can allow the attacker to execute arbitrary SQL commands, such as deleting data from the database or stealing sensitive information.

How Does SQL Injection Work?

SQL injection attacks typically exploit vulnerabilities in web applications that use user input to build SQL queries. This can happen when an application fails to properly validate or sanitize user input before using it to construct SQL queries.

For example, let's say a web application has a login page that asks users for their username and password. The application might construct a SQL query that looks like this:

SELECT * FROM users WHERE username='username' AND password='password'

If the application doesn't properly validate the username and password fields, an attacker could insert malicious SQL code into them. For example, the attacker could enter the following as their username:

' OR 1=1 --

This would result in the following SQL query being executed:

SELECT * FROM users WHERE username='' OR 1=1 --' AND password='password'

The "--" at the end of the input is a comment symbol in SQL, which tells the database to ignore anything that comes after it. This means that the attacker's input effectively bypasses the password check, allowing them to log in to the application as any user.

Preventing SQL Injection

Preventing SQL injection requires a multi-layered approach that involves both developers and system administrators. Here are some best practices that can help prevent SQL injection attacks:

  1. Use prepared statements: Prepared statements are a feature of most modern programming languages that allow developers to construct SQL queries in a safe way. With prepared statements, input values are passed as parameters to the query, rather than being concatenated directly into the SQL string. This makes it much harder for attackers to inject malicious code into the query.
  2. Use parameterized queries: Parameterized queries are similar to prepared statements, but they are used in situations where the query is executed multiple times with different input values. Like prepared statements, parameterized queries use placeholders for input values, which are then replaced with actual values when the query is executed.
  3. Validate user input: Validate user input at every point where it is used in the application. This includes input from web forms, query strings, cookies, and other sources.
  4. Sanitize user input: Sanitize user input by removing any characters that could be used to inject SQL code. This includes characters like single quotes, double quotes, and semicolons.
  5. Limit user privileges: Limit the privileges of database users to prevent them from executing arbitrary SQL commands.

Conclusion

SQL injection is a serious threat to web applications that use SQL databases. By exploiting vulnerabilities in the application's input validation mechanisms, attackers can execute malicious SQL code that can lead to data theft, data corruption, and other security issues.

Preventing SQL injection requires a multi-layered approach that involves developers and system administrators. By using prepared statements, parameterized queries, and validating and sanitizing

728x90
728x90

'해킹' 카테고리의 다른 글

SSTI 취약점  (0) 2022.01.11
Cross Site Scripting (XSS)  (0) 2021.12.22
Same Origin Policy & Cross Origin Resource Sharing  (0) 2021.12.20
OWASP top 10 / 2021 업데이트 한글 번역  (0) 2021.11.22
구글 도크 (Feat. 디렉토리 리스팅)  (0) 2021.10.14
728x90
728x90

Wireless Access Point

라즈베리파이에 Access Point를 만드는 방법(표현이 맞는지 모르겠지만)이 Routed와 Bridged 방식이 있는데

 

Bridged Wireless Access Point는 아래 그림과 같이 와이파이 확장기처럼 작동하고

                                         +- RPi -------+
                                     +---+ 10.10.0.2   |          +- Laptop ----+
                                     |   |     WLAN AP +-)))  (((-+ WLAN Client |
                                     |   |  Bridge     |          | 10.10.0.5   |
                                     |   +-------------+          +-------------+
                 +- Router ----+     |
                 | Firewall    |     |   +- PC#2 ------+
(Internet)---WAN-+ DHCP server +-LAN-+---+ 10.10.0.3   |
                 |   10.10.0.1 |     |   +-------------+
                 +-------------+     |
                                     |   +- PC#1 ------+
                                     +---+ 10.10.0.4   |
                                         +-------------+

 

Routed Wireless Access Point는 새로 내부망을 만드는 방식이다.

                                         +- RPi -------+
                                     +---+ 10.10.0.2   |          +- Laptop ----+
                                     |   |     WLAN AP +-)))  (((-+ WLAN Client |
                                     |   | 192.168.4.1 |          | 192.168.4.2 |
                                     |   +-------------+          +-------------+
                 +- Router ----+     |
                 | Firewall    |     |   +- PC#2 ------+
(Internet)---WAN-+ DHCP server +-LAN-+---+ 10.10.0.3   |
                 |   10.10.0.1 |     |   +-------------+
                 +-------------+     |
                                     |   +- PC#1 ------+
                                     +---+ 10.10.0.4   |
                                         +-------------+

Routed 방식은 Bridged 방식에서 몇가지 추가해야 하기 때문에 Bridged 방식으로 하였음

 

Bridged Wireless Access Point 라즈베리 파이 3 이상 또는 라즈베리 파이 제로 W의 내장 무선 기능을 사용하거나
Access Point Mode를 지원하는 적절한 USB 무선 동글을 사용하여 만들 수 있음

 

Install AP and Management Software

hostapd 패키지 설치가 필요함

sudo apt install hostapd

 

무선 액세스 지점 서비스를 활성화하고 Raspberry Pi가 부팅될 때 시작하도록 설정

sudo systemctl unmask hostapd
sudo systemctl enable hostapd

 

 

Setup the Network Bridge

다음 명령어로 파일을 생성하여 br0이라는 브리지 네트워크 장치를 추가

sudo nano /etc/systemd/network/bridge-br0.netdev

 

[NetDev]
Name=br0
Kind=bridge

 

다음 파일을 생성하여 이더넷 인터페이스(eth0)를 브리지 구성원으로 추가하여 이더넷 네트워크와 무선 네트워크를 연결

sudo nano /etc/systemd/network/br0-member-eth0.network
[Match]
Name=eth0

[Network]
Bridge=br0

 

systemd-networkd 서비스를 활성화하여 Raspberry Pi가 부팅될 때 브리지를 만들고 실행

sudo systemctl enable systemd-networkd

 

Define the bridge device IP configuration

Raspberry Pi의 DHCP 클라이언트인 dhcpcd는 모든 활성 인터페이스의 IP 주소를 자동으로 요청함
따라서 eth0 및 wlan0 인터페이스가 처리되지 않도록 차단하고 DHCP를 통해 dhcpcd가 br0만 구성하도록 해야 함

sudo nano /etc/dhcpcd.conf

시작 부분에 작성하기,"interface xxx line"가 있다면 그 위에 작성

denyinterfaces wlan0 eth0

파일의 마지막에 작성

interface br0

 

Ensure Wireless Operation

sudo nano /etc/hostapd/hostapd.conf
country_code=GB
interface=wlan0
bridge=br0
ssid=NameOfNetwork
hw_mode=g
channel=7
macaddr_acl=0
auth_algs=1
ignore_broadcast_ssid=0
wpa=2
wpa_passphrase=AardvarkBadgerHedgehog
wpa_key_mgmt=WPA-PSK
wpa_pairwise=TKIP
rsn_pairwise=CCMP

country_code는 위키백과 참조

ssid는 wifi 이름

hw_mode는 

  • a = IEEE 802.11a (5 GHz) (Raspberry Pi 3B+ onwards)
  • b = IEEE 802.11b (2.4 GHz)
  • g = IEEE 802.11g (2.4 GHz)

wpa_passphrase는 비밀번호 설정

 

Rasberry Pi를 다시 시작하고 무선 액세스 지점을 자동으로 사용할 수 있는지 확인

sudo systemctl reboot

 

사실 이렇게 만들어 봤자 신호가 너무 약해서 공유기를 대체할 수는 없는 것 같음

방 안에서 거실에 있는 와이파이 신호가 약할 때나 사용할 수 있을 듯

사실 공식문서 번역한 수준이고 모든 명령어 하나하나 뜯어보지 않아서 포스팅하기에 좀 부끄럽지만

다른 포스팅을 보는 것 보다 공식문서 보는게 좋고 (다른 포스팅 따라해봤지만 안됨)

번역 된 포스팅을 본적이 없어서 포스팅 하게 되었음

 

참조문헌

라즈베리파이 공식 문서

728x90
728x90
728x90
728x90